import pango
from pattern.crossref import CrossReference


#
# Layout Shapes
#
class Layout(object):

    #
    # Shape Toolkit
    #

    class Shape(object):
        def __init__(self):
            self.xref = { }

        def properties(self):
            return self.__dict__.keys()

        def __del__(self):
            import codebase
            codebase.bye(self)

    class Point(Shape):
        def at(self):
            """Returns (x,y) tuple of absolute coordinates"""
            return 0,0
        def __call__(self):
            return self.at()

    class AbsolutePoint(Point):

        def __init__(self, x, y):
            Layout.Shape.__init__(self)
            self.x, self.y = (x, y)

        def moverel(self, relx, rely):
            self.x += relx
            self.y += rely

        def at(self):
            return self.x,self.y

    class RelativePoint(Point):

        def __init__(self, x, y, origin):
            Layout.Shape.__init__(self)
            self.x = x
            self.y = y
            self.origin = CrossReference(self, origin)

        def at(self):
            ox, oy = self.origin().at()
            return ox + self.x, oy + self.y

        def moverel(self, relx, rely):
            self.x += relx
            self.y += rely

        def translate(absx, absy, origin):
            ox, oy = origin.at()
            return Layout.RelativePoint(absx - ox, absy - oy, origin)
        translate = staticmethod(translate)

    class PlaceableShape(Shape):

        def setOrigin(self, new_origin):
            pass

    class Rectangle(PlaceableShape):
        
        def __init__(self, left, top, width, height, origin = None):
            Layout.Shape.__init__(self)
            if origin is None:
                self.topleft = Layout.AbsolutePoint(left, top)
            else:
                self.topleft = Layout.RelativePoint.translate(left, top, origin)
            self.anchor = CrossReference(self, self.topleft)
            self.width, self.height = width, height

        def __getattr__(self, attr):
            if attr == "left":
                return self.topleft()[0]
            elif attr == "top":
                return self.topleft()[1]
            else:
                raise AttributeError, "'%s' object has no attribute '%s'" \
                      % (type(self).__name__, attr)

        def properties(self):
            return Layout.Shape.properties(self) + ["left", "top"]

        def setOrigin(self, new_origin):
            if new_origin is None:
                self.topleft = Layout.AbsolutePoint(self.left, self.top)
            else:
                self.topleft = Layout.RelativePoint.translate(self.left, self.top,
                                                              new_origin)
            self.anchor = CrossReference(self, self.topleft)
            if 'left' in self.__dict__: del self.left
            if 'top' in self.__dict__: del self.top

        def draw(self, window, gc):
            left, top = self.topleft()
            if self.width > 0 and self.height > 0:
                window.draw_rectangle(gc, False, left, top,
                                      self.width - 1, self.height - 1)

        def shade(self, window, gc):
            left, top = self.topleft()
            if self.width > 0 and self.height > 0:
                window.draw_rectangle(gc, True, left, top,
                                      self.width - 1, self.height - 1)

        def bound(self):
            return self

        def hittest(self, x, y):
            left, top = self.topleft()
            return x >= left and y >= top and \
                   x < left + self.width and y < top + self.height

        def topleft(self):
            return self.left, self.top

        def centre(self):
            left, top = self.topleft()
            return left + self.width / 2, top + self.height / 2

        def area(self):
            return self.width * self.height

        def moverel(self, relx, rely):
            if isinstance(self.topleft, Layout.Point):
                self.topleft.moverel(relx, rely)
            else:
                self.left += relx; self.top += rely

        def moveto(self, x, y):
            self.moverel(x - self.left, y - self.top)

        def intersects(self, other):
            s_left, s_top = self.topleft()
            o_left, o_top = other.topleft()
            return self._sectintersect((s_left, s_left + self.width),
                                       (o_left, o_left + other.width)) and \
                self._sectintersect((s_top, s_top + self.height),
                                    (o_top, o_top + other.height))

        def _sectintersect(self, section1, section2):
            s1,e1 = section1; s2, e2 = section2
            return self._insect(s1, section2) or self._insect(e1, section2) or \
                   self._insect(e2, section1) or self._insect(s2, section1)

        def _insect(self, point, section):
            return point <= section[1] and point >= section[0]

    class ConnectionPoint(Point):

        def __init__(self, rectangle, attachment):
            Layout.Shape.__init__(self)
            self.shape = CrossReference(self, rectangle)
            self.attachment = attachment

        def at(self):
            return self.attachment.at(self.shape())

        def draw(self, window, gc):
            x, y = self.at()
            window.draw_rectangle(gc, False, x - 1, y - 1, 2, 2)

        def bound(self):
            x, y = self.at()
            return Rectangle(x-1, y-1, 3, 3)

        def hittest(self, x, y):
            x0, y0 = self.at()
            return abs(x - x0) < 3 and abs(y - y0) < 3

    class Connector(Shape):

        def __init__(self, pointA, pointB):
            Layout.Shape.__init__(self)
            self.pointA = CrossReference(self, pointA)
            self.pointB = CrossReference(self, pointB)

        def points(self):
            return self.pointA(), self.pointB()

        def draw(self, window, gc):
            x1, y1 = self.pointA().at()
            x2, y2 = self.pointB().at()
            window.draw_line(gc, x1, y1, x2, y2)
            window.draw_arc(gc, False, (x1+x2)/2-4, (y1+y2)/2-4, 9, 9, 0, 360*64)

        def bound(self):
            x1, y1 = self.pointA().at()
            x2, y2 = self.pointB().at()
            return Layout.Rectangle(min(x1, x2), min(y1, y2),
                                    abs(x1 - x2), abs(y1 - y2))

        def hittest(self, x, y):
            x1, y1 = self.pointA().at()
            x2, y2 = self.pointB().at()
            xm, ym = (x1 + x2) / 2, (y1 + y2) / 2
            return abs(x - xm) < 9 and abs(y - ym) < 9

    class FreeFormConnector(Shape):

        def __init__(self, vertices):
            self.vertices = vertices

        def draw(self, window, gc):
            last_vertex = self.vertices[0]
            for vertex in self.vertices[1:]:
                x1, y1 = last_vertex
                x2, y2 = vertex
                window.draw_line(gc, x1, y1, x2, y2)
                last_vertex = vertex

        def bound(self):
            left, top = right, bottom = self.vertices[0]
            for x, y in self.vertices[1:]:
                left = min(left, x)
                right = max(left, x)
                top = min(top, y)
                bottom = max(bottom, y)
            return Layout.Rectangle(left, top, right-left, bottom-top)

    class Label(Shape):

        def __init__(self, anchor, caption, attachment):
            Layout.Shape.__init__(self)
            self.anchor = CrossReference(self, anchor)
            self.caption = caption
            self.attachment = attachment

        def topleft(self):
            x, y = self.anchor().at()
            if hasattr(self, 'attachment'):
                w, h = self.layout().get_pixel_size()
                bound0 = Layout.Rectangle(0, 0, w, h)
                relx, rely = self.attachment.at(bound0)
            else:
                relx, rely = 0, 0
            return x - relx, y - rely

        def layout(self):
            layout = Layout.lw.area.create_pango_layout("") # :-P
            layout.set_text(self.caption)
            return layout

        def draw(self, window, gc):
            x, y = self.topleft()
            window.draw_layout(gc, x, y, self.layout())

        def bound(self):
            x, y = self.topleft()
            w, h = self.layout().get_pixel_size()
            return Layout.Rectangle(x, y, w, h)

        def hittest(self, x, y):
            return self.bound().hittest(x, y)
    
    #
    # Graphical Notifications
    #

    class Notification(object):

        def __init__(self, shape):
            self.shape = shape

    
    class ShadeNotification(Notification):

        def draw(self, window, gc):
            self.shape.bound().shade(window, gc)

        def bound(self):
            return self.shape.bound()


    class BulletinNotification(Notification):

        SHADED = True

        def bullet(self):
            bound = self.shape.bound()
            return Layout.Rectangle(bound.left - 5, bound.top - 5, 11, 11)

        def draw(self, window, gc):
            if self.SHADED:
                self.bullet().shade(window, gc)
            else:
                self.bullet().draw(window, gc)

        def bound(self):
            return self.bullet().bound()


    class Icon(Shape):
        
        def __init__(self, topleft, xpm, sz = 32):
            self.topleft = topleft
            self.xpm = xpm
            self.sz = sz

        def pixmap(self):
            return lw.icons.load_icon(self.xpm, self.sz, 0)

        def draw(self, window, gc):
            pixmap = self.pixmap()
            x, y = self.topleft
            window.draw_pixbuf(gc, pixmap, 0, 0, x, y, -1, -1)

        def bound(self):
            pixmap = self.pixmap()
            left, top = self.topleft
            return Layout.Rectangle(left, top, pixmap.get_width(),
                                          pixmap.get_height())
            

    class Indicator(BulletinNotification):

        SHADED = False

        def bullet(self):
            return Layout.Icon(self.shape.bound().topleft(), "gtk")

        
    #
    # Attachment Geometry
    #
    
    class Attachment(object):
        pass

    class CentreAttachment(Attachment):
        def at(self, shape):
            return shape.centre()
        def __repr__(self):
            return "centre"

    class SideAttachment(Attachment):
        def __init__(self, percentage):
            self.percentage = percentage
        def __repr__(self):
            return "%s:%i%%" % (self.ID, int(self.percentage*100 + .5))

    class LeftSideAttachment(SideAttachment):
        ID = "left"
        def at(self, shape):
            return (shape.left, int(shape.top + shape.height * self.percentage))

    class RightSideAttachment(SideAttachment):
        ID = "right"
        def at(self, shape):
            return (shape.left + shape.width,
                    int(shape.top + shape.height * self.percentage))

    class TopSideAttachment(SideAttachment):
        ID = "top"
        def at(self, shape):
            return (int(shape.left + shape.width * self.percentage), shape.top)
        
    class BottomSideAttachment(SideAttachment):
        ID = "bottom"
        def at(self, shape):
            return (int(shape.left + shape.width * self.percentage),
                    shape.top + shape.height)

    attachments = {'centre': CentreAttachment(),
                   'top-left': TopSideAttachment(.0),
                   'top-right': TopSideAttachment(1.0),
                   'bottom-left': BottomSideAttachment(.0),
                   'bottom-right': BottomSideAttachment(1.0),
                   'left': LeftSideAttachment(.5),
                   'left:20%': LeftSideAttachment(.2),
                   'left:80%': LeftSideAttachment(.8),
                   'right': RightSideAttachment(.5),
                   'top': TopSideAttachment(.5),
                   'bottom': BottomSideAttachment(.5),
                   'bottom:20%': BottomSideAttachment(.2),
                   'bottom:80%': BottomSideAttachment(.8)}

    def attachment_expr(expr):
        import re
        attachments = Layout.attachments
        if expr in attachments:
            return attachments[expr]
        else:
            mo = re.match("(.*):(\d+)%", expr)
            if mo is not None:
                side, percentage = mo.groups()
                return type(attachments[side])(float(percentage) / 100.)
            else:
                raise ValueError, expr

    attachment_expr = staticmethod(attachment_expr)



# Serialization patch
Point = Layout.Point
AbsolutePoint = Layout.AbsolutePoint
RelativePoint = Layout.RelativePoint
Rectangle = Layout.Rectangle
ConnectionPoint = Layout.ConnectionPoint
Connector = Layout.Connector
FreeFormConnector = Layout.FreeFormConnector
Label = Layout.Label

CentreAttachment = Layout.CentreAttachment
LeftSideAttachment = Layout.LeftSideAttachment
RightSideAttachment = Layout.RightSideAttachment
TopSideAttachment = Layout.TopSideAttachment
BottomSideAttachment = Layout.BottomSideAttachment
